package it.antonio.ilpizze.ioc;

import it.antonio.ilpizze.aop.Aspect;
import it.antonio.ilpizze.ioc.builders.ConstantBuilder;
import it.antonio.ilpizze.ioc.builders.ListBuilder;
import it.antonio.ilpizze.ioc.builders.MapBuilder;
import it.antonio.ilpizze.ioc.builders.ObjectBuilder;
import it.antonio.ilpizze.ioc.util.FieldMatcher;
import it.antonio.ilpizze.ioc.util.Matcher;
import it.antonio.ilpizze.ioc.util.MethodMatcher;
import it.antonio.ilpizze.ioc.util.PropertyMatcher;
import it.antonio.ilpizze.reflection.FieldAccessor;
import it.antonio.ilpizze.reflection.MethodAccessor;
import it.antonio.ilpizze.reflection.PropertyAccessor;

import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

public abstract class Module {

	private List<Module> childrenModules = new ArrayList<Module>();

	private List<ConstantBuilder> constants = new LinkedList<ConstantBuilder>();
	private List<ObjectDefinition> objects = new LinkedList<ObjectDefinition>();
	private List<ListBuilder> lists = new LinkedList<ListBuilder>();
	private List<MapBuilder> maps = new LinkedList<MapBuilder>();

	private List<InstanceDecoratorDefinition> instanceDecorators = new LinkedList<InstanceDecoratorDefinition>();

	private Behavior behavior = new Behavior();

	protected abstract void configure();

	public final void contribute(Dependencies dependencies) {
		configure();

		for (Module child : childrenModules) {
			child.contribute(dependencies);
		}

		for (ConstantBuilder builder : constants)dependencies.define(builder);
		
		for (ObjectDefinition definition : objects) {
			Class<? extends Object> type = definition.type;
			Object key = definition.key;
			ScopeFactory<Object> scopeFactory = definition.scopeFactory;
			ConstructionFactory<Object> constructionFactory = definition.constructionFactory;
			TransformationFactory<Object> transformationFactory = definition.transformationFactory;
			List<DecorationFactory<Object>> decorationFactories = definition.decorationFactories;
			List<InjectionMonitor> monitors = definition.monitors;

			if (scopeFactory == null) scopeFactory = behavior.scopeFactory;
			if (constructionFactory == null) constructionFactory = behavior.constructionFactory;
			if (transformationFactory == null) transformationFactory = behavior.transformationFactory;
			for (DecorationFactory<Object> decFact : behavior.decorationFactories)
				decorationFactories.add(decFact);
			for (InjectionMonitor mon : behavior.monitors)
				monitors.add(mon);

			Scope<Object> scope = scopeFactory.scope(key, type);
			Construction<Object> construction = constructionFactory.construction(key, type);
			Transformation<Object> transformation = transformationFactory.transformation(key, type);
			List<Decoration<Object>> decorations = new LinkedList<Decoration<Object>>();
			for (DecorationFactory<Object> decorationFactory : decorationFactories)
				decorations.add(decorationFactory.decoration(key, type));

			dependencies.define(new ObjectBuilder(type, key, scope, construction, transformation, decorations, monitors));

		}

		for (ListBuilder builder : lists)dependencies.define(builder);
		
		for (MapBuilder builder : maps)dependencies.define(builder);
		
		for (InstanceDecoratorDefinition instanceDecorator : instanceDecorators){
			
			List<Decoration<? super Object>> decorations = new ArrayList<Decoration<? super Object>>();
			
			for(DecorationFactory<? super Object> factory: instanceDecorator.decorationFactories) {
				decorations.add(factory.decoration(null, instanceDecorator.type));
			}
			
			for (DecorationFactory<Object> factory : behavior.decorationFactories) {
				decorations.add(factory.decoration(null, instanceDecorator.type));
			}
			
			dependencies.define(new InstanceDecorator(instanceDecorator.type, decorations));
		}
		
	}

	// DEFINITIONS

	protected void module(Module module) {
		childrenModules.add(module);
	}

	protected void object(Class<? extends Object> clazz, Object... contributes) {
		ObjectDefinition definition = new ObjectDefinition(clazz);
		for (Object cont : contributes) {
			cont = tryConvertToFactory(cont);
			if (cont instanceof Class) {
				definition.type = (Class<Object>) cont;
				continue;
			}
			if (cont instanceof KeyDef) {
				definition.key = ((KeyDef) cont).key;
				continue;
			}
			if (cont instanceof ConstructionFactory) {
				definition.constructionFactory = (ConstructionFactory<Object>) cont;
				continue;
			}
			if (cont instanceof TransformationFactory) {
				definition.transformationFactory = (TransformationFactory<Object>) cont;
				continue;
			}
			if (cont instanceof DecorationFactory) {
				definition.decorationFactories.add((DecorationFactory<Object>) cont);
				continue;
			}
			if (cont instanceof InjectionMonitor) {
				definition.monitors.add((InjectionMonitor) cont);
				continue;
			}			
			if (cont instanceof ScopeFactory) {
				definition.scopeFactory = (ScopeFactory<Object>) cont;
				continue;
			}
			if (cont instanceof SingleBehaviorDef) {
				SingleBehaviorDef sbDef = (SingleBehaviorDef) cont;
				Matcher<?> matcher = sbDef.matcher;
				if(matcher instanceof FieldMatcher) { behavior.decorationFactories.add(((FieldMatcher) matcher).inject(definition.key)); continue;}
				if(matcher instanceof MethodMatcher) { behavior.decorationFactories.add(((MethodMatcher) matcher).inject(definition.key)); continue;}
				if(matcher instanceof PropertyMatcher) { behavior.decorationFactories.add(((PropertyMatcher) matcher).inject(definition.key)); continue;}
				
				
				throw new IocException("Bad configuration for SingleBehaviorDef");
			}
			throw new IocException("Bad configuration for Object");
		}
		
		objects.add(definition);
	}

	protected void constant(Object key, Object instance) {
		constants.add(new ConstantBuilder(key, instance));
	}

	protected void constant(Object instance) {
		constants.add(new ConstantBuilder(instance.getClass(), instance));
	}

	protected void list(Object key, ListDef listDef, Object... contributes) {

		ScopeFactory<List<? super Object>> scopeFactory = ScopeFactory.prototype();
		List<DecorationFactory<List<? super Object>>> decorationFactories = new LinkedList<DecorationFactory<List<? super Object>>>();
		List<InjectionMonitor> monitors = new LinkedList<InjectionMonitor>();

		for (Object cont : contributes) {
			cont = tryConvertToFactory(cont);
			if (cont instanceof DecorationFactory) {
				decorationFactories.add((DecorationFactory<List<? super Object>>) cont);
				continue;
			}
			if (cont instanceof InjectionMonitor) {
				monitors.add((InjectionMonitor) cont);
				continue;
			}
			if (cont instanceof ScopeFactory) {
				scopeFactory = (ScopeFactory<List<? super Object>>) cont;
				continue;
			}
			throw new IocException("Bad configuration for List");
		}

		Scope<List<? super Object>> scope = scopeFactory.scope(key, (Class<? extends List<? super Object>>) List.class);
		List<Decoration<List<? super Object>>> decorations = new ArrayList<Decoration<List<? super Object>>>();
		for (DecorationFactory<List<? super Object>> decFact : decorationFactories)
			decorations.add(decFact.decoration(key, (Class<? extends List<? super Object>>) List.class));

		lists.add(new ListBuilder(key, listDef, scope, decorations, monitors));

	}

	protected void map(Object key, MapDef<? extends Object, ? extends Object> mapDef, Object... contributes) {

		ScopeFactory<Map<? super Object, ? super Object>> scopeFactory = ScopeFactory.prototype();
		List<DecorationFactory<Map<? super Object, ? super Object>>> decorationFactories = new LinkedList<DecorationFactory<Map<? super Object, ? super Object>>>();
		List<InjectionMonitor> monitors = new LinkedList<InjectionMonitor>();

		for (Object cont : contributes) {
			cont = tryConvertToFactory(cont);
			if (cont instanceof DecorationFactory) {
				decorationFactories.add((DecorationFactory<Map<? super Object, ? super Object>>) cont);
				continue;
			}
			if (cont instanceof InjectionMonitor) {
				monitors.add((InjectionMonitor) cont);
				continue;
			}
			if (cont instanceof ScopeFactory) {
				scopeFactory = (ScopeFactory<Map<? super Object, ? super Object>>) cont;
				continue;
			}
			throw new IocException("Bad configuration for Map");
		}

		Scope<Map<? super Object, ? super Object>> scope = scopeFactory.scope(key, (Class<? extends Map<? super Object, ? super Object>>) Map.class);
		List<Decoration<Map<? super Object, ? super Object>>> decorations = new ArrayList<Decoration<Map<? super Object, ? super Object>>>();
		for (DecorationFactory<Map<? super Object, ? super Object>> decFact : decorationFactories)
			decorations.add(decFact.decoration(key, (Class<? extends Map<? super Object, ? super Object>>) Map.class));

		maps.add(new MapBuilder(key, mapDef, scope, decorations, monitors));

	}

	protected void decorateInstance(Class<? extends Object> type, DecorationFactory<?>... contributes) {
		List<DecorationFactory<? super Object>> decorationFactories = new ArrayList<DecorationFactory<? super Object>>();
		for (Object cont : contributes) {
			cont = tryConvertToFactory(cont);
			if (cont instanceof DecorationFactory) {
				decorationFactories.add((DecorationFactory<? super Object>) cont);
				continue;
			}
			throw new IocException("Bad configuration for Instance Decorator");
		}
		instanceDecorators.add(new InstanceDecoratorDefinition(type, decorationFactories));
		
	}
	
	protected void allObjects(Object... contributes) {
		for (Object cont : contributes) {
			cont = tryConvertToFactory(cont);
			if (cont instanceof ConstructionFactory) {
				behavior.constructionFactory = (ConstructionFactory<Object>) cont;
				continue;
			}
			if (cont instanceof TransformationFactory) {
				behavior.transformationFactory = (TransformationFactory<Object>) cont;
				continue;
			}
			if (cont instanceof DecorationFactory) {
				behavior.decorationFactories.add((DecorationFactory<Object>) cont);
				continue;
			}
			if (cont instanceof InjectionMonitor) {
				behavior.monitors.add((InjectionMonitor) cont);
				continue;
			}
			if (cont instanceof ScopeFactory) {
				behavior.scopeFactory = (ScopeFactory<Object>) cont;
				continue;
			}
			
			throw new IocException("Bad configuration for Object Behavior");
		}
	}

	

	protected void on(DecorationFactory<? super Object> decorationFact) {
		behavior.decorationFactories.add(decorationFact);
	}
	protected void construction(ConstructionFactory<Object> cf) {
		behavior.constructionFactory = cf;
	}
	public SingleBehaviorDef injectOn(Matcher<?> matcher) {
		return new SingleBehaviorDef(matcher);
	}
	
	
	// MATCHERS
	protected FieldMatcher fieldsWithName(final String name) {
		return new FieldMatcher() {
			@Override
			public boolean match(FieldAccessor field) {
				return field.getName().equals(name);
			}
		};
	}

	protected FieldMatcher fieldsAnnotatedWith(final Class<? extends Annotation> ann) {
		return new FieldMatcher() {

			@Override
			public boolean match(FieldAccessor field) {
				return field.hasAnnotation(ann);
			}
		};
	}

	protected FieldMatcher fieldsOfType(final Class<?> type) {
		return new FieldMatcher() {

			@Override
			public boolean match(FieldAccessor field) {
				return field.getType().equals(type);
			}
		};
	}

	protected FieldMatcher fieldMatcher(final Matcher<FieldAccessor> delegate) {
		return new FieldMatcher() {

			@Override
			public boolean match(FieldAccessor field) {
				return delegate.match(field);
			}
		};
	}

	protected MethodMatcher methodsWithName(final String name) {
		return new MethodMatcher() {
			@Override
			public boolean match(MethodAccessor method) {
				return method.getName().equals(name);
			}
		};
	}

	protected MethodMatcher methodsAnnotatedWith(final Class<? extends Annotation> ann) {
		return new MethodMatcher() {

			@Override
			public boolean match(MethodAccessor method) {
				return method.isAnnotationPresent(ann);
			}
		};
	}

	protected MethodMatcher methodsWithParameterTypes(final Class<?>... types) {
		return new MethodMatcher() {

			@Override
			public boolean match(MethodAccessor method) {
				return types.equals(method.getParameterTypes());
			}
		};
	}

	protected MethodMatcher methodMatcher(final Matcher<MethodAccessor> delegate) {
		return new MethodMatcher() {
			@Override
			public boolean match(MethodAccessor method) {
				return delegate.match(method);
			}
		};
	}

	protected PropertyMatcher propertiesWithName(final String name) {
		return new PropertyMatcher() {
			@Override
			public boolean match(PropertyAccessor prop) {
				return prop.getName().equals(name);
			}
		};
	}

	protected PropertyMatcher propertiesSetterAnnotatedWith(final Class<? extends Annotation> ann) {
		return new PropertyMatcher() {

			@Override
			public boolean match(PropertyAccessor prop) {
				return prop.getSetter().isAnnotationPresent(ann);
			}
		};
	}

	protected PropertyMatcher propertiesOfType(final Class<?> type) {
		return new PropertyMatcher() {

			@Override
			public boolean match(PropertyAccessor prop) {
				return prop.getType().equals(type);
			}
		};
	}

	protected PropertyMatcher propertyMatcher(final Matcher<PropertyAccessor> delegate) {
		return new PropertyMatcher() {

			@Override
			public boolean match(PropertyAccessor prop) {
				return delegate.match(prop);
			}
		};
	}

	// UTILS
	// CONTRIBUTES

	protected KeyDef key(final Object key) {
		return new KeyDef(key);
	}

	protected <T> ConstructionFactory<T> autowireConstructor() {
		return ConstructionFactory.autowire();
	}

	protected <T> ConstructionFactory<T> autowireConstructorProxy(final Aspect... aspects) {
		return ConstructionFactory.autowireProxy(aspects);
	}

	protected <T> ConstructionFactory<T> emptyConstructor() {
		return ConstructionFactory.emptyConstructor();
	}

	
	
	protected <T> ScopeFactory<T> singleton() {
		return ScopeFactory.singleton();
	}

	protected <T> ScopeFactory<T> prototype() {
		return ScopeFactory.prototype();
	}

	protected <T> ScopeFactory<T> threadLocal() {
		return ScopeFactory.threadlocal();
	}
	
	protected <T> DecorationFactory<T> initMethod(Matcher<MethodAccessor> matcher) {
		return DecorationFactory.initMethod(matcher);
	}

	
	
	
	protected <T> ConstructionFactory<T> construct(Construction<T> c){
		return ConstructionFactory.fromConstruction(c);
	}
	protected <T> ConstructionFactory<T> construct(ConstructionNoParams<T> c){
		return ConstructionFactory.fromConstruction(c);
	}
	protected <T> TransformationFactory<T> transform(Transformation<T> t){
		return TransformationFactory.fromTransformation(t);
	}
	protected <T> DecorationFactory<T> decorate(Decoration<T> d){
		return DecorationFactory.fromDecoration(d);
	}
	protected <T> DecorationFactory<T> decorate(DecorationNoParams<T> d){
		return DecorationFactory.fromDecoration(d);
	}
	protected <T> ScopeFactory<T> scope(Scope<T> s){
		return ScopeFactory.fromScope(s);
	}
	
	
	// UTILS
	
	private class KeyDef {
		private Object key;

		public KeyDef(Object key) {
			this.key = key;
		}

	}
	private class SingleBehaviorDef {
		private Matcher<?> matcher;

		public SingleBehaviorDef(Matcher<?> matcher) {
			this.matcher = matcher;
		}
		
		
	}

	private Object tryConvertToFactory(Object cont) {
		if (cont instanceof Construction) {
			return ConstructionFactory.fromConstruction((Construction<?>) cont);
		}
		if (cont instanceof Transformation) {
			return TransformationFactory.fromTransformation((Transformation<?>) cont);
		}
		if (cont instanceof Decoration) {
			return DecorationFactory.fromDecoration((Decoration<?>) cont);
		}
		if (cont instanceof Scope) {
			return ScopeFactory.fromScope((Scope<?>) cont);
		}
		return cont;
	}

	private class ObjectDefinition {
		private Class<? extends Object> type;
		private Object key;

		private ScopeFactory<Object> scopeFactory;
		private ConstructionFactory<Object> constructionFactory;
		private TransformationFactory<Object> transformationFactory;
		private List<DecorationFactory<Object>> decorationFactories = new ArrayList<DecorationFactory<Object>>();
		private List<InjectionMonitor> monitors = new ArrayList<InjectionMonitor>();

		public ObjectDefinition(Class<? extends Object> type) {
			this.key = type;
			this.type = type;
		}

	}
	private class InstanceDecoratorDefinition {
		private Class<? extends Object> type;
		private List<DecorationFactory<? super Object>> decorationFactories;
		
		private InstanceDecoratorDefinition(Class<? extends Object> type, List<DecorationFactory<? super Object>> decorationFactories) {
			this.type = type;
			this.decorationFactories = decorationFactories;
		}
		
	}

	private class Behavior {
		private ScopeFactory<Object> scopeFactory = ScopeFactory.prototype();
		private ConstructionFactory<Object> constructionFactory;
		private TransformationFactory<Object> transformationFactory = TransformationFactory.identity();
		private List<DecorationFactory<? super Object>> decorationFactories = new ArrayList<DecorationFactory<? super Object>>();
		private List<InjectionMonitor> monitors = new ArrayList<InjectionMonitor>();

	}
	
	@FunctionalInterface
	public static interface ConstructionNoParams<T> extends Construction<T>{
		
		default T construct(Class<? extends T> type, Dependencies dependencies) {
			return construct();
		}

		public T construct();
	}
	
	@FunctionalInterface
	public static interface DecorationNoParams<T> extends Decoration<T>{
		
		default void decorate(T instance, Dependencies dependencies) {
			decorate();
		}

		public void decorate();
	}
	

}
